﻿using System;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.ServiceProcess;
using Reactor.Customization;
using Reactor.Environment;
using Reactor.Exceptions;
using Reactor.Hosting;
using Reactor.Providers;
using Reactor.ServiceGrid.Repositories;
using Reactor.ServiceGrid.ServiceEntities;
using Reactor.ServiceGrid.Tools;
using ServiceInstaller = Reactor.ServiceGrid.Tools.ServiceInstaller;

namespace Reactor.CoreHost
{
    public class CoreServiceHost : ServiceHostBase
    {
        #region Fields

        private const string ServiceDescription = "Reactor Core Service. Responsible for managing services participating in the Reactor Services Platform.";
        private ICoreDataProvider _coreDataProvider;
        private const string ServiceName = "Reactor Core";

        #endregion

        private void InitializePlatform()
        {
            // Ensure no other Reactor Core processes are running on this machine
            EnsureNoOtherCoreProcesses();

            // Allow an oppurtunity to customize initialization.
            // For all ReactorCores, the machine name is used for the primary input queue on the service bus.
            InitializationCustomizationManager.PrimaryEndpointName = "Reactor.Core." + System.Environment.MachineName;
            var customizationManager = new InitializationCustomizationManager();
            if (Log.IsDebugEnabled) Log.DebugFormat("There were {0} initialization customizers found", customizationManager.Customizers.Count);
            customizationManager.RunAll();
            Log.Info("Initilization customization complete.");

            // Make assertions for required types
            AssertThatServiceLocatorExists();
            AssertThatServiceBusExists();

            _coreDataProvider = Context.ServiceLocator.GetInstance<ICoreDataProvider>();
            
            // Create a service package repository instance
            var connectionData = Context.ServiceLocator.GetInstance<PlatformConnectionData>();
            var packageRepository = PackageRepositoryFactory.Default.CreateRepository(connectionData.PackageRepositoryLocation);
            Context.GridContext.ServicePackageRepository = packageRepository;
        }

        private static void EnsureNoOtherCoreProcesses()
        {
            var processCount = Process.GetProcesses().Where(p => p.ProcessName.StartsWith("Reactor.CoreHost") && !p.ProcessName.StartsWith("Reactor.CoreHost.vshost")).Count();
            if(processCount > 1)
                throw new FatalException("Only one Reactor Core process can be running on a single machine at a time.");
        }

        #region Overrides of ServiceHostBase

        /// <summary>
        /// Initializes this instance.
        /// </summary>
        public override void RunManagedService(bool runAsConsole)
        {
            if (Log.IsDebugEnabled) Log.DebugFormat("RunManagedService called. RunAsConsole flag: {0}", runAsConsole);

            InitializePlatform();

            Context.GridContext.CurrentReactorService = new ReactorCore(_coreDataProvider);

            if (runAsConsole)
            {
                Log.Debug("Starting ReactorService directly because of console flag.");

                Context.GridContext.CurrentReactorService.Start();

                Console.WriteLine("Type 'exit' to quit");

                while (Console.ReadLine() != "exit")
                { }

                Context.GridContext.CurrentReactorService.Stop();
            }
            else
            {
                if (Log.IsDebugEnabled) Log.DebugFormat("Handing off execution to the Windows Service. Service name: {0}", Context.GridContext.CurrentReactorService.Identifier.ToServiceInstanceString());

                var windowsServiceWrapper = new ServiceWrapper(Context.GridContext.CurrentReactorService);
                ServiceBase.Run(windowsServiceWrapper);

                if (Log.IsDebugEnabled) Log.Debug("ServiceBase.Run called.");
            }
        }

        public override void Install()
        {
            var servicePath = Assembly.GetEntryAssembly().Location;

            if (Log.IsDebugEnabled) Log.DebugFormat("Attempting to install Windows Service: {0}", ServiceName);

            if (ServiceInstaller.InstallService(servicePath, ServiceName, ServiceName, ServiceDescription, true, false))
                Log.Debug("Windows Service installed.");
            else
                Log.Error("Failed to install Windows Service.");
        }

        public override void Uninstall()
        {
            if (Log.IsDebugEnabled) Log.DebugFormat("Attempting to uninstall Windows Service: {0}", ServiceName);

            if (!ServiceInstaller.UnInstallService(ServiceName))
                Log.ErrorFormat("Failure reported by ServiceInstaller while attempting to uninstall service: {0}", ServiceName);
        }

        #endregion
    }
}
